1. 為什麼需要自訂錯誤
在前幾天的學習中,Result<T, E> 的 E 通常是 io::Error 或 String,但在實際專案裡,常會遇到多種錯誤來源(I/O、格式錯誤、邏輯錯誤等)。這時就需要自訂錯誤型別來整合不同錯誤情況,方便統一處理與擴充。
2. 定義自訂錯誤列舉
use std::fmt;
use std::io;
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(std::num::ParseIntError),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO error: {}", e),
AppError::Parse(e) => write!(f, "Parse error: {}", e),
}
}
}
impl From<io::Error> for AppError {
fn from(err: io::Error) -> AppError {
AppError::Io(err)
}
}
impl From<std::num::ParseIntError> for AppError {
fn from(err: std::num::ParseIntError) -> AppError {
AppError::Parse(err)
}
}
3. 在函式中使用自訂錯誤
fn read_and_parse() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")?; // 可能回傳 io::Error
let number: i32 = content.trim().parse()?; // 可能回傳 ParseIntError
Ok(number)
}
fn main() {
match read_and_parse() {
Ok(n) => println!("Read number: {}", n),
Err(e) => println!("Error: {}", e),
}
}
輸出:
Error: IO error: 系統找不到指定的檔案。 (os error 2)
或:
Error: Parse error: invalid digit found in string
4. 為什麼要實作 From 與 Display
From 的實作讓 ? 可以自動把不同錯誤轉換成你的自訂錯誤。Display 則讓錯誤能以人類可讀的方式輸出。
這樣一來,整個錯誤處理流程就更乾淨,不需要到處手動轉換錯誤型別或使用 unwrap。
5. 學習心得與補充
今天的學習讓我更深刻地體會到 Rust 的錯誤處理並不只是防呆機制,以前在 C++中,我常常只是單純地用 try-catch 包住錯誤,或回傳一個錯誤碼了事,但在 Rust 裡,錯誤本身被視為型別系統的一部分,設計得更具結構性。當我開始實作自己的 AppError 時,發現可以清楚地定義錯誤來源、讓編譯器協助我轉換錯誤,這種體驗比單純印錯誤訊息更可靠。再加上 ? 的自動傳遞機制,讓程式既安全又簡潔。透過這次練習,我開始理解 Rust 對錯誤處理的設計理念不只是防止出錯,而是幫助開發者更好地理解與掌握錯誤。